/* -----------------------------------------------------------------------------------------
* Web_DesignationXML Class - Used by the Web_DesignationXML VF Page to generate an XML output
* of CommonGround Designations. This data is used by the FRB Website and is accessed via
* sites at http://frb.force.com/web/Web_DesignationXML
*
* Created by: M.Smith/Idealist Consulting, 25 November 2009
*
* Modifications:
* - M.Smith, 01/13/2010: Rewrite this to use Dynamic SOQL to query all fields in the FRB_Programs__c
*       object. Run one query of this object to avoid blowing through governor limits. Include
*       sub-queries for the related lists (lead members, implementing members, supporting gp's)
*
* QueryToXML
*   ?SOQL=Select ID, Name, AccountID, Account.Name, CreatedDate, CloseDate, IsClosed From Opportunity Where isClosed
*   &ReferencedObjects=Account

* /apex/QueryToXML?SOQL=Select+ID%2C+Name%2C+AccountID%2C+Account.Name%2C+CreatedDate%2C+CloseDate%2C+IsClosed+From+Opportunity+Where+isClosed=TRUE&ReferencedObjects=Account
*
 ------------------------------------------------------------------------------------------- */
public class QueryToXML {

    XmlStreamWriter w;
    string soqlString = '';
    string referencedObjects = '';
    string ParseErrorMessage = null;
    static final string ERROR_NOQUERY = 'ERROR: No Query String Was Found';
    static final string ERROR_QUERY = 'QUERY ERROR: ';
    static final string COPYRIGHT = 'Generated From QueryToXML Utility. Created by Force2b, LLC, 08-Feb-2010';

	// Generate a Global Map of all Objects in the System
	static final Map<String, Schema.SObjectType> allObjects = Schema.getGlobalDescribe();

    public QueryToXML() {
    	try {
	    	soqlString = ApexPages.currentPage().getParameters().get('soql');
            referencedObjects = ApexPages.currentPage().getParameters().get('ReferencedObjects');
	    	soqlString = System.Encodingutil.urlDecode(soqlString, 'UTF-8');
    	} catch (exception e) {
    		this.ParseErrorMessage = e.getMessage();
    	}
    }

    public String getsoqlResults() {

        if (this.ParseErrorMessage == '') return genErrorXML(this.ParseErrorMessage);
        if (this.soqlString == '' || this.soqlString == null) return genErrorXML(ERROR_NOQUERY);

        Integer rowCount = 0;

        // split out the Query to get the FROM part
        String fieldlist = soqlString.substring(7,soqlString.toUpperCase().indexOf(' FROM'));
        string tableName = soqlString.subString(soqlString.toUpperCase().indexOf(' FROM')+6);
        tableName = tableName.split(' ',2)[0];
        String[] fields = fieldlist.split(',',250);

        system.debug(LoggingLevel.ERROR, ' - soqlString=' + soqlString);
        system.debug(LoggingLevel.ERROR, ' - tableName=' + tableName);
        system.debug(LoggingLevel.ERROR, ' - fields=' + fields);

        // find the actual sObject associated with the Table being queried
		Schema.SObjectType thesobject = null;
		for (String s : allObjects.keySet()) {
		   if (s.toUpperCase() == tableName.toUpperCase()) thesobject = allObjects.get(s);
		}

        // Get a full list of the Fields in this object
		Schema.DescribeSObjectResult sdescribe = thesobject.getDescribe();
		Map<String, Schema.SObjectField> objFields = sdescribe.fields.getMap();

        // Build a Map of referencedBy Objects
        Map<String, Map<String, Schema.SObjectField>> refObjects = New Map<String, Map<String, Schema.SObjectField>>();

        // Parse out the list of referenced objects as passed on the URL
        // this helps us know which objects to look for so we can limit our calls to getDescribed()
		if (referencedObjects != null) {
		    for (String r : referencedObjects.split(',',10)) {
		        for (String s : allObjects.keySet()) {
		          if (s.toUpperCase() == r.toUpperCase()) {
			         Schema.DescribeSObjectResult refObjDescribe = allObjects.get(s).getDescribe();
			         refObjects.put(s, refObjDescribe.fields.getMap());
		          }
		       }
		    }
		}

        // Run the query
        Sobject[] results = null;
        try {
            results = Database.Query(soqlString);
        } catch (exception ex) {
            return genErrorXML(ERROR_QUERY + ex.getMessage());
        }

        this.w = new XmlStreamWriter();

        w.writeStartDocument(null, '1.0');
        w.writeComment(COPYRIGHT + '\r\rOutput Generated on ' + dateTime.now() + '\r' + 'Query: ' + soqlString);
        w.writeStartElement('m', tableName, tableName);
        w.writeNamespace('m', tableName);

        // Query the full object in one call so we can look this up later on
        //SObject sobj = Database.query(soqlString);
        // String objType = sobj.getSObjectType();
        integer nRecordNumber = 0;
		for (sObject r : results) {

            w.writeStartElement(null, 'Record', null);
            w.writeAttribute(null, null, 'id', (string)r.Get('id'));

		    // for each field being queried
			for (string f : fields) {
		        String relatedObjName  = '';
		        String relatedObjField  = '';
		        f = f.trim();

		        if (f.contains('.')) {
		        	// related object
		        	relatedObjName = f.split('\\.',2)[0];
		        	relatedObjField = f.split('\\.',2)[1];
		        	Map<String, Schema.SObjectField> relObjFields = refObjects.get(relatedObjName);
		        	writeElement(f, r.getSObject(relatedObjName).get(relatedObjField));
		        } else {
		        	// in this object
		        	writeElement(f, r.get(f));
		        }
			}

			w.writeEndElement(); // end of record
			nRecordNumber++;
		}

        w.writeEndElement(); //end table
        w.writeEndDocument();
        String xmlOutput = w.getXmlString();
        w.close();

        return xmlOutput;
    }

    private void writeElement(string e, object data) {
            w.writeStartElement(null, e, null);
            w.writeCharacters(objectToString(data));
            w.writeEndElement();
    }

    private string genErrorXML(string errMessage) {
        this.w = new XmlStreamWriter();

        w.writeComment(COPYRIGHT);
        if (this.soqlString != null) w.writeComment(this.soqlString);
        w.writeStartDocument(null, '1.0');
        w.writeStartElement('m', 'ERROR', 'ERROR');
        w.writeNamespace('m', 'ERROR');
        w.setDefaultNamespace('http://www.defns.com');

            w.writeStartElement(null, 'ERROR', null);
            w.writeAttribute(null, null, 'MESSAGE', errMessage);
            w.writeEndElement();

        w.writeEndElement();
        w.writeEndDocument();
        String xmlOutput = w.getXmlString();
        w.close();

        return xmlOutput;
    }

    private string objectToString(object data) {
        string d = null;
        integer n = 0;
        while (d == null && n < 9) {
            try {
                if (n == 0) d = (string)data;
                else if (n == 1) { boolean v1 = ((boolean)data); d = (v1) ? 'TRUE' : 'FALSE'; break; }
                else if (n == 2) { decimal v2 = (decimal)data; d = v2.format(); break; }
                else if (n == 3) { double v3 = (double)data; d = v3.format(); break; }
                else if (n == 4) { integer v4 = (integer)data; d = v4.format(); break; }
                else if (n == 5) { date v5 = (date)data; d = dateTime.newInstance(v5.year(), v5.month(), v5.day()).formatGMT('yyyy-MM-dd'); break; }
                else if (n == 6) { datetime v6 = (datetime)data; d = v6.formatGMT('yyyy-MM-dd') + 'T' + v6.formatGMT('HH:mm:ss') + 'Z'; break; }
                else if (n == 7) { blob v7 = (blob)data; d = v7.toString(); break; }
                else if (n == 8) { d = ''; }
            } catch (exception ex) {
            	system.debug(LoggingLevel.ERROR, '- Conversion Error(' + data + '):' + ex.getMessage());
            }
            n++;
        }
        return d;
    }

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // Unit Test Logic
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    public static TestMethod void QueryToXML_Test() {

        QueryToXML queryXML1 = new QueryToXML();
        String result1 = queryXML1.getsoqlResults();

        PageReference pg = Page.QueryToXML;
        pg.getParameters().put('soql', 'Select ID, Name, BadField, CreatedDate, CreatedByID, Owner.Name, Industry, Website, AnnualRevenue FROM Account');
        pg.getParameters().put('referencedObjects', 'Account,User');
        Test.setCurrentPage(pg);

        QueryToXML queryXML = new QueryToXML();
        String result = queryXML.getsoqlResults();

    }
}